// Created by xiongxuan on 2/10/18.
//
//
#include <cstdio>
#include <cstdlib>
#include <getopt.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <unistd.h>
#include "judger.h"
#include "core.h"

/*
 * 解析参数
 */
static
void parse_judger_arguments(int argc, char **argv) {
    int opt;
    extern char *optarg;

    while ((opt = getopt(argc, argv, "c:r:d:")) != -1) {
        switch (opt) {
//            case 'c': PROBLEM::code_path    = optarg;       break;
//            case 't': PROBLEM::time_limit   = atoi(optarg); break;
//            case 'm': PROBLEM::memory_limit = atoi(optarg); break;
//            case 's': PROBLEM::spj          = true;         break;
//            case 'S': PROBLEM::spj_lang     = atoi(optarg); break;
//            case 'd': PROBLEM::run_dir      = optarg;       break;
//            case 'i': PROBLEM::input_file   = optarg;   printf("input_file = %s\n", optarg);    break;
//            case 'o': printf("output_file = %s\n", optarg);PROBLEM::output_file  = optarg;       break;
//            default:
//                FM_LOG_WARNING("Unknown option provided: -%c %s", opt, optarg);
//                exit(JUDGE_CONF::EXIT_BAD_PARAM);
            case 'c': PROBLEM_SET::code_path    = optarg;   printf("judger: code_path = %s\n", PROBLEM_SET::code_path.c_str());break;
            case 'r': PROBLEM_SET::run_dir      = optarg;   printf("judger: run_dir = %s\n", PROBLEM_SET::run_dir.c_str()); break;
            case 'd': PROBLEM_SET::data_path    = optarg;   printf("judger: data_path = %s\n", PROBLEM_SET::data_path.c_str());break;
            case 'p': PROBLEM_SET::cur_path     = optarg;   printf("judger: cur_path = %s\n", PROBLEM_SET::cur_path.c_str());break;
            default:
                printf("Unknown option provided: -%c %s", opt, optarg);
                exit(-JUDGE_CONF::EXIT_BAD_PARAM);
        }
    }
}

/**
 * 输入重定向
 * @param path
 */
void inputRedirect(const char *path) {
    fclose(stdin);
    stdin = freopen(path, "r", stdin);
    if (stdin == NULL) {
        printf("It occur a error when freopen: stdin(%p) path(%s)", stdin, path);
        exit(JUDGE_CONF::EXIT_PRE_JUDGE);
    }
}

/**
 * 输出重定向
 * @param path
 */
void outputRedirect(const char *path) {
    stdout = freopen(path, "w", stdout);
    if (stdout == NULL) {
        printf("It occur a error when freopen: stdin(%p) path(%s)", stdin, path);
        exit(JUDGE_CONF::EXIT_PRE_JUDGE);
    }
}

/**
 * 读取题目配置文件
 * @param fp
 */
void readConfig() {
//    inputRedirect(PROBLEM_SET::config_path.c_str());
    std::ifstream cin(PROBLEM_SET::config_path.c_str());
    std::string testCaseInputPath = PROBLEM_SET::data_path + "/input/";
    std::string testCaseOutputPath = PROBLEM_SET::data_path + "/output/";

    cin >> PROBLEM_SET::judge_type >> PROBLEM_SET::time_limit >> PROBLEM_SET::memory_limit;
    if (PROBLEM_SET::judge_type == 1) {
        // 如果判题类型为1(得分型), 则把所有的测试点读入vector
        cin >> PROBLEM_SET::test_case_count;

        for (int i = 0; i < PROBLEM_SET::test_case_count; i++) {
            PROBLEM_SET::TEST_CASE testCase;
            cin >> testCase.input >> testCase.output >> testCase.score;

            // 因为读入的是相对路径, 要把它改为绝对路径
            testCase.input = testCaseInputPath + testCase.input;
            testCase.output = testCaseOutputPath + testCase.output;
            PROBLEM_SET::test_case_vector.push_back(testCase);
        }
    }
    else {
        cin >> PROBLEM_SET::single_case.input >> PROBLEM_SET::single_case.output;
        PROBLEM_SET::single_case.input = testCaseInputPath + PROBLEM_SET::single_case.input;
        PROBLEM_SET::single_case.output = testCaseOutputPath + PROBLEM_SET::single_case.output;
    }

    printf("type = %d\n", PROBLEM_SET::judge_type);
    printf("time limit = %d  memory limit = %d\n", PROBLEM_SET::time_limit, PROBLEM_SET::memory_limit);
}

/**
 * 通过execlp运行程序
 * @param sourcePath
 * @param timeLimit
 * @param memoryLimit
 * @param runDir
 * @param stdInputPath
 * @param stdOutputPath
 */
int startJudgeCore(const char *sourcePath, bool reCompile, int timeLimit, int memoryLimit, const char *runDir,
                   const char *stdInputPath, const char *stdOutputPath) {
    std::string cmd = PROBLEM_SET::judger_core_dir;
    cmd += " -c ";
    cmd += sourcePath;
    cmd += (reCompile ? " -C " : " ");
    cmd += " -t ";
    cmd += std::to_string(timeLimit);
    cmd += " -m ";
    cmd += std::to_string(memoryLimit);
    cmd += " -d ";
    cmd += runDir;
    cmd += " -i ";
    cmd += stdInputPath;
    cmd += " -o ";
    cmd += stdOutputPath;
    std::cout << cmd << std::endl;

    int status = system(cmd.c_str());
    return status;
}

/**
 * 从judgerCore生成的result.txt中读取测试结果
 */
void readTestCaseResult() {
    std::ifstream cin(PROBLEM_SET::core_result_dir.c_str());
    cin >> PROBLEM::result;

    getline(cin, PROBLEM::status); // 读掉缓冲区的'\n'字符
    getline(cin, PROBLEM::status);
    cin >> PROBLEM::time_usage >> PROBLEM::memory_usage;

    if (1 == PROBLEM::result) { // 如果是编译错误
        std::string str((std::istreambuf_iterator<char>(cin)), // 读取文件内的剩余内容
                        std::istreambuf_iterator<char>());
        PROBLEM::extra_message = str;
    }
}

/**
 * 将scoreJudge的测试结果写出到文件
 * @param testCaseResultVector
 */
void writeResultForScoreJudge(const std::vector <PROBLEM_SET::TEST_CASE_RESULT> &testCaseResultVector) {
    printf("now write result for score judge\n");
    std::ofstream cout(PROBLEM_SET::judger_result_dir);

    if (1 == PROBLEM::result) {
        // 编译错误
        printf("\n编译错误\n");
        cout << PROBLEM::result << std::endl << PROBLEM::status
             << std::endl << PROBLEM::time_usage << std::endl
             << PROBLEM::memory_usage << std::endl << PROBLEM::extra_message << std::endl;
    }
    else {
        // 其他情况
        printf("\n其他情况\n");
        for (int i = 0; i < testCaseResultVector.size(); i++) {
            printf("test case %d: ", i);
            printf("%d%s ", testCaseResultVector[i].result, testCaseResultVector[i].status.c_str());

        }
    }
}

void writeResultForCorrectJudge() {
    printf("now write result for correct judge\n");
    std::ofstream cout(PROBLEM_SET::judger_result_dir);

    if(1 == PROBLEM::result) {
        cout << PROBLEM::result << std::endl << PROBLEM::status
             << std::endl << PROBLEM::time_usage << std::endl
             << PROBLEM::memory_usage << std::endl << PROBLEM::extra_message << std::endl;
    }
    else {
        cout << PROBLEM::result << std::endl << PROBLEM::status
             << std::endl << PROBLEM::time_usage << std::endl
             << PROBLEM::memory_usage << std::endl ;
    }
}


void scoreJudge() {
    printf("do score startJudgeCore\n");

    int scoreSum = 0, flag = 0;
    std::vector <PROBLEM_SET::TEST_CASE_RESULT> testCaseResultVector; //存放测试结果
    PROBLEM_SET::TEST_CASE_RESULT testCaseResultTemp;

    // 遍历所有的test case
    for (PROBLEM_SET::iter = PROBLEM_SET::test_case_vector.begin();
         PROBLEM_SET::iter != PROBLEM_SET::test_case_vector.end();
                 PROBLEM_SET::iter++) {
        // 第二个参数的意思是, 如果是第一个测试点, 则需要把源代码编译,后续的测试点不需要再次编译
        if (startJudgeCore(PROBLEM_SET::code_path.c_str(), PROBLEM_SET::iter == PROBLEM_SET::test_case_vector.begin(),
                           PROBLEM_SET::time_limit, PROBLEM_SET::memory_limit,
                           PROBLEM_SET::run_dir.c_str(), (*PROBLEM_SET::iter).input.c_str(),
                           (*PROBLEM_SET::iter).output.c_str()) < 0) {
            perror("Cannot run judger core!");
            exit(JUDGE_CONF::EXIT_PRE_JUDGE);
        }

        readTestCaseResult();

        switch (PROBLEM::result) {
            case 7:     // AC
                scoreSum += (*PROBLEM_SET::iter).score;
            case 2:     // TLE
            case 3:     // MLE
            case 4:     // OLE
            case 5:     // RE
            case 6:     // WA
            case 8:     // PE
                testCaseResultTemp.result = PROBLEM::result;
                testCaseResultTemp.time_usage = PROBLEM::time_usage;
                testCaseResultTemp.memory_usage = PROBLEM::memory_usage;
                testCaseResultTemp.case_score = (*PROBLEM_SET::iter).score;
                testCaseResultVector.push_back(testCaseResultTemp);
                std::cout << "case " << PROBLEM_SET::iter - PROBLEM_SET::test_case_vector.begin() << ": "
                          << PROBLEM::status << " " << testCaseResultTemp.time_usage << " "
                          << testCaseResultTemp.memory_usage << " " << testCaseResultTemp.case_score << std::endl;
                break;
            case 1:     // CE
            default:
                flag = 1; // 编译错误和系统错误发生后, 终止评测
                break;
        }

        if (1 == flag)
            break;
    }
    if (0 == flag) {
        // 不是编译错误或者系统错误
        std::cout << "all score is " << scoreSum << std::endl;
    }
    else {
        std::cout << "result: " << PROBLEM::result << std::endl << PROBLEM::extra_message << std::endl;
        std::cout << "编译错误或者系统错误" << std::endl;
    }
}

void correctJudge() {
    printf("do correct startJudgeCore\n");

    if (startJudgeCore(PROBLEM_SET::code_path.c_str(), true,
                       PROBLEM_SET::time_limit, PROBLEM_SET::memory_limit,
                       PROBLEM_SET::run_dir.c_str(), PROBLEM_SET::single_case.input.c_str(),
                       PROBLEM_SET::single_case.output.c_str()) < 0) {
        perror("Cannot run judger core!");
        exit(JUDGE_CONF::EXIT_PRE_JUDGE);
    }
}

int main (int argc, char *argv[]) {
    //为了执行判题程序，必须要有root权限
//    if (geteuid() != 0) {
//        printf("You must run this program as root.");
//        exit(JUDGE_CONF::EXIT_UNPRIVILEGED);
//    }

    parse_judger_arguments(argc, argv);

    // 设置一些其他的参数
    PROBLEM_SET::config_path = PROBLEM_SET::data_path + "/config.ini";
    PROBLEM_SET::judger_core_dir = PROBLEM_SET::cur_path + "/judgerCore";
    PROBLEM_SET::core_result_dir = PROBLEM_SET::run_dir + "/result_case_temp.txt";
    PROBLEM_SET::judger_result_dir = PROBLEM_SET::run_dir + "/result.txt";
    printf("judger: config_path = %s\n", PROBLEM_SET::config_path.c_str());
    printf("judger: judger_core_dir = %s\n", PROBLEM_SET::judger_core_dir.c_str());
    printf("judger: core_result_dir = %s\n", PROBLEM_SET::core_result_dir.c_str());
    printf("judger: judger_result_dir = %s\n", PROBLEM_SET::judger_result_dir.c_str());

    readConfig();
    if (1 == PROBLEM_SET::judge_type){
        scoreJudge();
    }
    else if(2 == PROBLEM_SET::judge_type){
        correctJudge();
        readTestCaseResult();
        writeResultForCorrectJudge();
    }

    return 0;
}